package com.beetle123.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.beetle123.model.ConfigModel;
import com.beetle123.model.MethodModel;
import com.beetle123.model.ProjectModel;
import com.beetle123.model.ServiceModel;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ReferenceConfig;
import org.apache.dubbo.rpc.service.GenericService;
import redis.clients.jedis.*;

import java.net.URLDecoder;
import java.util.*;

/**
 * @author yuanlong.chen
 * @date 2021/09/06
 */
public class DubboUtils {

    private static ApplicationConfig applicationConfig = new ApplicationConfig("dubbo-caller");

    private static boolean isStartWith(String path, List<String> startWithPathList) {
        if (startWithPathList == null || startWithPathList.isEmpty()) {
            return true;
        }
        for (String item : startWithPathList) {
            if (path.startsWith(item)) {
                return true;
            }
        }
        return false;
    }

    private static Map<String, String> parseParam(String url) {
        String params = url.substring(url.indexOf("?") + 1);
        String[] paramArray = StringUtils.split(params, "&");
        Map<String, String> paramMap = new HashMap<>(8);
        for (String param : paramArray) {
            String[] kv = StringUtils.split(param, "=");
            paramMap.put(kv[0], kv[1]);
        }
        return paramMap;
    }

    public static Map<String, ProjectModel> getProjectService(ConfigModel config) throws Exception {
        CuratorFramework client = null;
        Map<String, ProjectModel> projectMap = new HashMap<>(8);
        try {
            ExponentialBackoffRetry retry = new ExponentialBackoffRetry(1000, 3);
            client = CuratorFrameworkFactory.builder()
                    .connectString(config.getZk())
                    .connectionTimeoutMs(10 * 1000)
                    .sessionTimeoutMs(10 * 1000)
                    .retryPolicy(retry).build();
            client.start();
            List<String> list = client.getChildren().forPath("/dubbo");
            for (String item : list) {
                if (!isStartWith(item, config.getServicePrefixList())) {
                    continue;
                }
                List<String> apiList = client.getChildren().forPath("/dubbo/" + item + "/providers");
                for (String tmp : apiList) {
                    String url = URLDecoder.decode(tmp, "utf-8").substring(8);
                    Map<String, String> paramMap = parseParam(url);
                    ProjectModel project = projectMap.get(paramMap.get("application"));
                    if (project == null) {
                        project = new ProjectModel(config.getEnv(), paramMap.get("application"));
                        projectMap.put(paramMap.get("application"), project);
                    }
                    project.addIpPort(url.substring(0, url.indexOf("/")));
                    ServiceModel service = new ServiceModel();
                    service.setName(url.substring(url.indexOf("/") + 1, url.indexOf("?")));
                    service.setGroup(paramMap.get("group"));
                    service.setVersion(paramMap.get("version"));
                    String key = service.getName() + ":" + service.getVersion() + ":" + service.getGroup() + ":provider:" + project.getAppName();
                    service.setUniqueKey(key);
                    project.addService(key, service);
                }
            }
            JedisCluster jedis = getJedis(config.getRedis());
            for (ProjectModel project : projectMap.values()) {
                for (Map.Entry<String, ServiceModel> entry : project.getServiceMap().entrySet()) {
                    String json = jedis.get(entry.getValue().getUniqueKey() + ".metaData");
                    System.out.println(json);
                    JSONObject jsonObject = JSON.parseObject(json);
                    JSONArray methodArray = jsonObject.getJSONArray("methods");
                    for (Object methodItem : methodArray) {
                        JSONObject obj = (JSONObject) methodItem;
                        MethodModel method = new MethodModel();
                        method.setName(obj.getString("name"));
                        method.setReturnType(obj.getString("returnType"));
                        JSONArray paramTypes = obj.getJSONArray("parameterTypes");
                        method.setParameterTypes(paramTypes.toArray(new String[paramTypes.size()]));
                        entry.getValue().addMethod(method);
                    }
                }
            }
            System.out.println(JSON.toJSONString(projectMap));
            client.close();
        } catch (Exception e) {
            throw e;
        } finally {
            if (client != null) {
                client.close();
            }
        }
        return projectMap;
    }

    private static JedisCluster getJedis(String redisUrl) {
        URL url = URL.valueOf(redisUrl);
        Set<HostAndPort> jedisClusterNodes = new HashSet<>();
        List<URL> urls = url.getBackupUrls();
        for (URL tmpUrl : urls) {
            jedisClusterNodes.add(new HostAndPort(tmpUrl.getHost(), tmpUrl.getPort()));
        }
        JedisCluster jedisCluster = new JedisCluster(jedisClusterNodes, 60 * 1000, 60 * 1000, 2, url.getPassword(), new GenericObjectPoolConfig());
        return jedisCluster;
    }

    public static Object invoke(String ipPort, String service, String version, String group, MethodModel method, String reqParam) {
        if (StringUtils.isBlank(version)) {
            version = "1.0.0";
        }
        if (StringUtils.isBlank(group)) {
            group = "dubbo";
        }

        ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
        reference.setGeneric(true);
        reference.setApplication(applicationConfig);
        reference.setInterface(service);
        reference.setVersion(version);
        reference.setUrl("dubbo://" + ipPort);
        reference.setTimeout(5 * 60 * 1000);
        reference.setGroup(group);
        try {
            removeGenericSymbol(method.getParameterTypes());
            GenericService genericService = reference.get();
            Object[] args = new Object[]{};
            if( method.getParameterTypes() != null &&  method.getParameterTypes().length > 0){
                args = new Object[]{JSON.parseObject(reqParam)};
            }
            return genericService.$invoke(method.getName(), method.getParameterTypes(), args);
        } finally {
            reference.destroy();
        }

    }

    private static void removeGenericSymbol(String[] parameterTypes) {
        for (int i = 0; i < parameterTypes.length; i++) {
            int index = parameterTypes[i].indexOf("<");
            if (index > -1) {
                parameterTypes[i] = parameterTypes[i].substring(0, index);
            }
        }
    }
}
